package com.weijian.service.impl;

import com.weijian.dto.ChunkUploadRequest;
import com.weijian.dto.CompleteUploadRequest;
import com.weijian.dto.InitUploadRequest;
import com.weijian.entity.UploadTask;
import com.weijian.entity.VideoInfo;
import com.weijian.service.UploadTaskService;
import com.weijian.service.VideoInfoService;
import io.minio.ComposeObjectArgs;
import io.minio.ComposeSource;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 直接分片上传到MinIO的服务
 * 无需本地临时文件存储，分片直接上传到MinIO，最后使用MinIO的Compose API合并
 */
@Slf4j
@Service
public class DirectMinIOChunkUploadService {

    @Autowired
    private MinioClient minioClient;

    @Autowired
    private MinIOServiceImpl minIOService;

    @Autowired
    private VideoInfoService videoInfoService;

    @Autowired
    private UploadTaskService uploadTaskService;

    @Value("${minio.bucket-name}")
    private String bucketName;

    // 内存中存储上传状态（生产环境可改为Redis）
    private final Map<String, Set<Integer>> uploadedChunksCache = new ConcurrentHashMap<>();

    /**
     * 初始化上传任务
     */
    public Object initUpload(InitUploadRequest request) {
        String fileMd5 = request.getFileMd5();
        String objectName = fileMd5 + ".mp4";

        // 检查MinIO中是否已存在相同文件
        try {
            if (minIOService.exists(objectName)) {
                return "文件已存在，无需重复上传";
            }
        } catch (Exception e) {
            log.error("检查文件存在性失败", e);
        }

        // 创建上传任务
        UploadTask task = uploadTaskService.getUploadTask(request.getTaskId());
        if (task == null) {
            task = uploadTaskService.initUploadTask(
                    request.getTaskId(),
                    request.getFileName(),
                    request.getFileSize(),
                    request.getFileMd5(),
                    request.getChunkSize(),
                    request.getTotalChunks()
            );
        }

        // 获取已上传的分片列表
        Set<Integer> uploadedChunks = getUploadedChunksFromMinIO(request.getTaskId(), request.getTotalChunks());
        uploadedChunksCache.put(request.getTaskId(), uploadedChunks);

        return new ChunkUploadServiceImpl.UploadInitResponse(task.getTaskId(), new ArrayList<>(uploadedChunks));
    }

    /**
     * 上传单个分片到MinIO
     */
    public Object uploadChunk(ChunkUploadRequest request) {
        String taskId = request.getTaskId();
        Integer chunkNumber = request.getChunkNumber();
        MultipartFile chunk = request.getChunk();

        UploadTask task = uploadTaskService.getUploadTask(taskId);
        if (task == null) {
            throw new RuntimeException("上传任务不存在");
        }

        if (task.getStatus() == 2) {
            return "文件已上传完成";
        }

        try {
            // 生成MinIO中的分片对象名
            String chunkObjectName = getChunkObjectName(task.getFileMd5(), chunkNumber);

            // 直接上传到MinIO
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(chunkObjectName)
                            .stream(chunk.getInputStream(), chunk.getSize(), -1)
                            .contentType("application/octet-stream")
                            .build()
            );

            log.info("分片 {} 上传到MinIO成功: {}", chunkNumber, chunkObjectName);

            // 更新缓存和数据库
            uploadedChunksCache.computeIfAbsent(taskId, k -> ConcurrentHashMap.newKeySet()).add(chunkNumber);
            uploadTaskService.updateChunkProgress(taskId, chunkNumber);

            return "分片上传成功";

        } catch (Exception e) {
            log.error("分片上传到MinIO失败: {}", e.getMessage(), e);
            uploadTaskService.markTaskFailed(taskId);
            throw new RuntimeException("分片上传失败: " + e.getMessage());
        }
    }

    /**
     * 完成上传，合并所有分片
     */
    @Transactional
    public VideoInfo completeUpload(CompleteUploadRequest request) {
        String taskId = request.getTaskId();
        UploadTask task = uploadTaskService.getUploadTask(taskId);

        if (task == null) {
            throw new RuntimeException("上传任务不存在");
        }

        if (task.getStatus() != 1) {
            throw new RuntimeException("上传任务状态异常");
        }

        try {
            String fileMd5 = task.getFileMd5();
            String finalObjectName = fileMd5 + ".mp4";
            int totalChunks = task.getTotalChunks();

            // 检查是否所有分片都已上传
            Set<Integer> uploadedChunks = uploadedChunksCache.get(taskId);
            if (uploadedChunks == null || uploadedChunks.size() < totalChunks) {
                throw new RuntimeException("分片不完整，无法合并");
            }

            // 使用MinIO的Compose API合并分片
            List<ComposeSource> sources = new ArrayList<>();
            for (int i = 0; i < totalChunks; i++) {
                String chunkObjectName = getChunkObjectName(fileMd5, i);
                sources.add(ComposeSource.builder()
                        .bucket(bucketName)
                        .object(chunkObjectName)
                        .build());
            }

            minioClient.composeObject(
                    ComposeObjectArgs.builder()
                            .bucket(bucketName)
                            .object(finalObjectName)
                            .sources(sources)
                            .build()
            );

            log.info("文件合并完成: {}", finalObjectName);

            // 清理分片文件
            cleanupChunks(fileMd5, totalChunks);

            // 创建视频信息记录
            VideoInfo videoInfo = new VideoInfo();
            videoInfo.setTitle(task.getFileName());
            videoInfo.setFileName(task.getFileName());
            videoInfo.setFilePath(finalObjectName);
            videoInfo.setFileSize(task.getFileSize());
            videoInfo.setDuration(0);
            videoInfo.setThumbnailPath("");
            videoInfo.setUploadTime(LocalDateTime.now());

            videoInfoService.save(videoInfo);
            uploadTaskService.markTaskComplete(taskId);

            return videoInfo;

        } catch (Exception e) {
            log.error("完成上传失败: {}", e.getMessage(), e);
            uploadTaskService.markTaskFailed(taskId);
            throw new RuntimeException("完成上传失败: " + e.getMessage());
        }
    }

    /**
     * 获取已上传的分片列表
     */
    public List<Integer> getUploadedChunks(String taskId) {
        UploadTask task = uploadTaskService.getUploadTask(taskId);
        if (task == null) {
            throw new RuntimeException("上传任务不存在");
        }

        Set<Integer> uploadedChunks = uploadedChunksCache.get(taskId);
        if (uploadedChunks == null) {
            uploadedChunks = getUploadedChunksFromMinIO(taskId, task.getTotalChunks());
            uploadedChunksCache.put(taskId, uploadedChunks);
        }

        return new ArrayList<>(uploadedChunks);
    }

    /**
     * 从MinIO获取已上传的分片列表
     */
    private Set<Integer> getUploadedChunksFromMinIO(String taskId, int totalChunks) {
        Set<Integer> uploadedChunks = new HashSet<>();
        UploadTask task = uploadTaskService.getUploadTask(taskId);
        if (task == null) return uploadedChunks;

        String fileMd5 = task.getFileMd5();

        try {
            for (int i = 0; i < totalChunks; i++) {
                String chunkObjectName = getChunkObjectName(fileMd5, i);
                if (minIOService.exists(chunkObjectName)) {
                    uploadedChunks.add(i);
                }
            }
        } catch (Exception e) {
            log.error("获取已上传分片列表失败", e);
        }

        return uploadedChunks;
    }

    /**
     * 获取分片在MinIO中的对象名
     */
    private String getChunkObjectName(String fileMd5, int chunkNumber) {
        return "chunks/" + fileMd5 + "/part-" + String.format("%06d", chunkNumber);
    }

    /**
     * 清理分片文件
     */
    private void cleanupChunks(String fileMd5, int totalChunks) {
        try {
            for (int i = 0; i < totalChunks; i++) {
                String chunkObjectName = getChunkObjectName(fileMd5, i);
                minioClient.removeObject(
                        io.minio.RemoveObjectArgs.builder()
                                .bucket(bucketName)
                                .object(chunkObjectName)
                                .build()
                );
            }
            log.info("清理分片文件完成: {}", fileMd5);
        } catch (Exception e) {
            log.warn("清理分片文件失败: {}", e.getMessage());
        }
    }
}